Memory Train


In this first tutorial we will develop a game called "Memory Train." No, it will not involve motorized strolls down Memory Lane, and neither will we deal with wagonloads of microchips. This game will train your memory, hence the title.

Contents



1. An overview of the game creation process


The story of every game begins with an idea, an initial metaphorical spark in the designer's brain. This is the one creative impulse out of which the entire project evolves. It is vital that we hold on to that initial idea throughout the entire process of designing, implementing, and testing. Only when we hold on to that first creative impulse will we later be able to determine if we are still on track and take corrective measures if it turns out that we are not. Such an initial idea will usually fit into one small paragraph of text, often a single sentence.

Before we dive into programming tasks, let us talk briefly about the process that leads from that initial idea to the finished game. This process involves a number of activities, including gameplay design, story design, sound design, implementation, and testing. For the purposes of describing the game creation process, let us assume that each of these activities is carried out by another person.

The central role is played by the gameplay designer because her job is to create the high-level design of how the game will operate. If we were to design a card game, for instance, the gameplay designer would primarily concern herself with creating the rules of the game. Here are some questions the gameplay designer would need to ask herself: How many cards does the game use? Will it be a trump game? Will there be suits and ranks? How will the score be determined? How long should the typical game take? Will it be possible to play against the computer? Against a human opponent at the same computer? Over the network? All of these questions can be answered without drawing a single picture, without recording a single sound effect, and without writing a single line of code.

In determining the high-level design, the gameplay designer generates the input required by most of the other team members. For example, the programmer needs to know the exact rules of the game in order to be able to translate them into code.

Eventually, the combined effort of programmer and sound designer will culminate in a finished prototype. This is in turn closely inspected by the gameplay designer to ensure that the finished product will meet her expectations. In case the prototype does not live up to the gameplay designer's expectations, the appropriate changes will be performed by the various team members and a new prototype is created. This loop continues until the gameplay designer determines that the resulting prototype is in fact the game she designed. Finally, as with any piece of software, the game will have to go through the various stages of alpha and beta testing to ensure its quality. Alpha testing is performed behind closed doors by a dedicated team of testers, while beta testing is performed by potential customers volunteering to give your product a try before it is finally released.

2. Designing Memory Train


For this project the initial idea is as follows:

The objective in our game is to repeat sequences of tones which get more complex as the game progresses.

Here's how it works: The game is played using the arrow keys, and each of the four arrow keys is associated with a specific tone. When the game begins, the computer randomly plays one of the four tones, and the player has to hit the corresponding arrow key. Next, the computer plays that first tone again and then adds a second tone, and the player has to hit the two corresponding arrow keys in sequence. If the player gets it right, the computer adds yet a third tone, and the player has to repeat all three tones in sequence by hitting the corresponding keys. This process continues until the player makes a mistake by either pressing the wrong key or failing to react within a certain amount of time. When the player makes a mistake the computer will play a special sound after which the final score will be announced. The final score is the number of tones which the player was able to repeat in sequence.

Here's an example:
The computer plays the tone for the up arrow key.
The player hits the up arrow key.
The computer plays the tones for up arrow and left arrow, one after the other.
The player hits up arrow then left arrow.
The computer plays the tones for up arrow, left arrow, left arrow.
The player hits up arrow, left arrow, left arrow.
The computer plays the tones for up, left, left, down.
The player presses up arrow, right arrow, thereby making a mistake.
The game ends, and the final score is 3 points because the longest sequence the player successfully repeated consisted of three tones.

A key aspect of successfully playing this game is memorizing which arrow key is tied to which tone. To make this as easy as possible we will implement a keyboard practice mode in which the player can press the arrow keys and listen to the tones they generate without any time constraints. To enter this practice mode, the player selects the appropriate option from a menu. Not only will this make our game look considerably more professional but it will also allow me to explain how to create menus in BGT. And if you thought it would end there, let me inform you that our menu will also have background music.

Exercise:

Reread the above game design and create a list of the sounds the game will contain.

Here is my solution to the above exercise:
- Four sounds for the four different tones
- An error sound to play when the player makes a mistake
- A music loop to play when the menu is active

Since this is a tutorial about programming rather than sound design, you will need to create the sounds that you wish to use for the game. In the below code, we have selected the filenames that we use for each of these sounds. The four tones are named 1.wav, 2.wav, 3.wav and 4.wav, the error sound is named error.wav, and the music loop is named, appropriately enough, music.wav.

3. Implementing Memory Train


3.1 Preparations


It is almost time to actually start programming! But before diving into source code, let us get some preliminaries out of the way.

Given the fact that you are reading this tutorial, it is quite likely that you have already installed BGT on your computer. But just in case you haven't, now is the time to do so.

Next, please create a new, empty directory for this project, and place the sounds you created inside this directory.

Finally, open your favorite text editor and create an empty text file called memory_train.bgt in the newly created directory. Some text editors, including Notepad, have a tendency to add a .txt extension to any file name that does not already end in .txt. A tried and tested remedy for this is to include the file name in double quotes. Another way to prevent the automatic .txt extension is to choose "all files" in the file type field before clicking the "Save" button.

3.2 The smallest possible BGT script


If you have studied the language tutorial, you will know that every BGT script contains one or more functions. The word "function" comes from the Latin "functio" which means "execution" or "performance." In programming, a function is some code that performs a well-defined task.

A function, in performing its task, can call other functions to carry out subtasks. The important thing to keep in mind here is that when one function calls another, the calling function is not finished. This leads to the rather peculiar fact that several functions may be executing at the same time but only one of them is in control. As an example from daily life, let us assume that the function "get_dressed" has called the function "put_on_socks". Now the function "put_on_socks" is in control but the function "get_dressed" is not finished. Instead, it is patiently waiting in the wings for "put_on_socks" to finish. As soon as this happens, "get_dressed" is back in control and continues executing where it left off, which will be just after the call to "put_on_socks".

I strongly urge you to reread the previous paragraph until you understand it completely. One of the main reasons why people find programming confusing is that they have misunderstood the concept of functions and function calls. In case you are still confused, the next time you put on your socks you will feel a warm glow inside and the concept of function calls will make perfect sense.

As an added bonus, once a function finishes it may return a value to its caller. This is appropriately called the function's return value, or simply its result.

It is well worth our time to take a look at the smallest BGT script which could possibly be written. For although it stops almost immediately after it starts and does exactly nothing in between, it yet provides the structure, or framework, of any other BGT script including Memory Train. Since it will eventually become part of Memory Train anyway, now is also the time to copy it into your memory_train.bgt file. Here it is, in full:

void main()
{
}

This defines a single function called main. The word "void" specifies that this function has no return value, which makes sense once you realize that it is the solitary function in this script and thus has nobody to return anything to. But its lack of a return value has a much more practical reason, and this has to do with the special significance of the name "main". The main function is special in that it is where execution of every BGT script begins, and the execution of every BGT script ends when the main function finishes. The two braces you see above embrace between themselves the entire execution of your script.

3.3 A script that does something


Let us now make our script actually do something by placing some instructions between those braces.

Exercise:

Find the alert function in the BGT reference, and with the help of the reference modify your script to display a message box. The title of the message box should be "Important Information", and the message text should be "Hello, I am John Doe, and I am a programmer." Instead of John Doe, put your own name into the message.

Hint on using the reference: The "alert" function is part of BGT's foundation layer.

Your script should now look something like this:

void main()
{
alert("Important Information", "Hello, I am Jane Smith, and I am a programmer.");
}

To execute your script, simply save the file, locate it in Windows Explorer and hit enter on it. Note that this only works when BGT is installed.

In case you didn't notice, the line:
alert("Important Information", "Hello, I am Jane Smith, and I am a programmer.");
is a function call. The function being called is "alert", and the calling function is "main". You can see that to call a function, we write its name, followed by a pair of parentheses in which we specify the information we would like to pass to the function, and finally a semicolon. In this case we pass two pieces of information to the "alert" function: a title and a message. There is a technical term for pieces of information passed to a function, by the way. They are called parameters.

Let's see what happens if we add another function call. Our new script is as follows:

void main()
{
alert("Important Information", "Hello, I am Jane Smith, and I am a programmer.");
alert("How many roads must a man walk down?", "The answer is 42.");
}

When you run this script, you will notice that, as you might expect, it displays two message boxes, one after the other. This serves to illustrate the point I made above, namely, that the main function is not stopped when it calls the alert function. As soon as the alert function has finished doing its first job, control flows back into the main function which then proceeds to call the alert function again.

3.4 Making decisions


A BGT script can do much more than carry out instructions in sequence. Sooner or later our scripts must learn to make decisions based on what the user does. Only then can we develop interactive scripts, and interactivity is, after all, what differentiates a game from a play.

Look at the following script:

void main()
{
question("A personal question", "Do you believe in the flying spaghetti monster?");
}

When you execute this script, it displays a message box similar to the one displayed by a call to alert. However, this time the solitary OK button is replaced by a Yes and a No button.

Exercise:

Read the entry on the question function in the BGT reference.

Asking the user a question without reacting to the answer is pretty pointless, so we will now modify the script to make a decision based on which of the two buttons the user clicks. As you learned by consulting the reference, the "question" function returns a value of 1 for the Yes button, 2 for the No button, and 0 if an error occurs.

To keep track of this return value, we will introduce a variable. A variable is a named container for a value. It is called variable because the value it contains may change.

To define a variable, we write its type followed by its name followed by a semicolon, like so:
int answer;

This defines a variable named "answer" which can hold a value of type "int". "int" is shorthand for "integer" and means a whole number, i.e. a number without a decimal point. If you would like to learn about the various other types supported by BGT, let me refer you to the language tutorial.

When we have defined a variable, we can assign a value to it by writing the name of the variable, an equals sign, the value we would like to assign, and finally a semicolon:
answer = 42;

In our running example, we don't want to place the number 42 in our variable. Instead, we would like our variable to store the value that the "question" function returned. We achieve this by simply replacing our 42 with the call to the "question" function:

void main()
{
int answer;
answer = question("A personal question", "Do you believe in the flying spaghetti monster?");
alert("Thank you!", "Your answer was " + answer + ". See you later.");
}

If you try out this script, you will notice that you get different messages depending on which of the two buttons you choose. This is because the variable "answer" receives the value returned by the "question" function. So this is in fact our first interactive script.

Note also how we used the plus sign to tie several things together into something larger:
"Your answer was " + answer + ". See you later."
Scanning this from left to right, we find some text in double quotes, a plus sign, a variable name, another plus sign, and some more text in double quotes. In this case, the plus signs serve to tie these three things together into the message we would like to display, like tying together three strings of pearls into a longer string of pearls. Programmers refer to a piece of text as a string because it is easily visualized as a string of characters. To use this term in context, we have just added a string, a number, and another string to form our message, which is itself a string.

You might be wondering, if the plus sign is used for tying strings together, how would you add two numbers? The surprising answer is that the plus sign is used for that purpose as well. The plus sign is a symbol which may refer to different activities, depending on context. In technical terms, the plus sign is an overloaded operator.

In our running example, our script is now able to react differently depending on which button the user clicks, but our reactions are not yet very meaningful. Wouldn't it be great if we could display completely different messages depending on the user's response? The following script does exactly that:

void main()
{
int answer;
answer = question("A personal question", "Do you believe in the flying spaghetti monster?");
if(answer == 1)
{
alert("How interesting!", "Thank you for being that honest.");
}
else if(answer == 2)
{
alert("I thought so!", "Now don't tell me the invisible pink unicorn got you first.");
}
else
{
alert("Whoops!", "Something is dreadfully wrong here! Maybe it's the monster taking revenge.");
}
}

This script uses the "if" statement to make a decision based on the value of the "answer" variable. If the value is 1, the first message is displayed. Otherwise, if the value is 2, the second message is displayed. Finally, if the value is neither 1 nor 2, the third message is displayed, indicating that an error has occurred.

Keep in mind that the keyword "if" is always followed by a condition in parentheses, and that in this case the closing parenthesis is never, I repeat, never, followed by a semicolon.

A condition is something which can either be true or false. This is in line with the usage of the word in every-day English. For example, the condition for going by train is that you are in possession of a valid ticket. If the condition is false you may not board the train.

The condition to test whether two things are equal looks like this:
a == b
Note the use of a double equal sign. This is required because the single equal sign is already used for something else.

Exercise:

What is the single equal sign used for? Hint: The answer is contained earlier in this tutorial.

Note that we have enclosed every branch of the above "if" statement in its own pair of braces. Strictly speaking, this is necessary only if a branch consists of multiple instructions. Since all three branches in the above script consist of a single instruction, namely, a call to "alert", we could just as well have written the following:

void main()
{
int answer;
answer = question("A personal question", "Do you believe in the flying spaghetti monster?");
if(answer == 1)
alert("Ramen to you, then!", "Thank you for being that honest.");
else if(answer == 2)
alert("I thought so!", "Now don't tell me the invisible pink unicorn got you first.");
else
alert("Whoops!", "Something is dreadfully wrong here! Maybe it's the monster taking revenge.");
}

Over the years of my programming career I have formed the habit of always including the braces from the start even in such cases where they are not strictly necessary. This way, when I add more statements at a later date I never have to worry about whether or not to add another pair of braces.

Just so that you know, the braces in a function definition are mandatory. For instance, the definition of your main function will always contain braces even if the function consists of a single instruction---in fact, even if it should consist of no instructions at all!

Exercise:

Read the relevant parts of the language tutorial to familiarize yourself with the various programming constructs for making decisions. In particular, pay close attention to the if, while, and for statements as they will be used in this tutorial.

3.5 A menu for Memory Train


In our quest for theoretical background we have digressed quite a bit from our original endeavor, which was to implement the game we designed in the previous chapter. Now is the time to get back to it.

Let's go back to our simplest of scripts, which was this:

void main()
{
}

Exercise:

The helper layer contains a helpful tool for creating menus. Can you find it in the reference?

The tool we are after is called dynamic_menu. It allows us to easily put together a menu in which the player can select an item with the up and down arrow keys and activate it by pressing enter. As you undoubtedly know, such menus abound in audiogames, with typical menu items being "start game", "test speakers", "options", and "exit game".

Let's take a moment to summarize what you already know about functions and variables.


A variable is a named container for a value which may change.
Every variable has a type, and while the value of a variable may change, the type never will. This means that an int variable will only ever store values of type int, and a string variable will only ever store strings. This may sound awfully restrictive, but it also means less work for the computer and fewer opportunities for programming errors. Besides, why should you want to use a single variable for vastly different purposes when you can have as many variables as you like?

In this section we will go one step further and talk about a kind of variable which has other variables and functions living inside it. A value with variables and functions inside it is called an object, and a variable holding an object is called an object variable, just as a variable holding an int is called an int variable. If this all sounds just a bit confusing, let me assure you that it will quickly become second nature to you once you see it in action. In just a few paragraphs you will realize that objects are all about simplicity.

Examine the following script:

#include "dynamic_menu.bgt"

void main()
{
show_game_window("Memory Train");
dynamic_menu menu;
menu.add_item_tts("Start game");
menu.add_item_tts("Keyboard practice");
menu.add_item_tts("Exit game");
menu.allow_escape = true;
menu.wrap = true;
menu.run("Please choose a menu item with the arrow keys, then hit enter to activate it.", true);
}

Exercise:

With the help of the reference, try to figure out what the above script will do. Test your hypothesis by running the script. Note: This script contains some constructs we have not covered yet, but they will all be explained below.

Let's start with the elements you already recognize. First, you will immediately have noticed that this is your standard main function with a sequence of instructions between the braces. Next, examine the following line:
show_game_window("Memory Train");
This is some name followed by a pair of parentheses with some data between them, and finally a semicolon. What do we call such a thing? Yes, a function call.

Exercise:
How many parameters does the above function call have?

Exercise:
Find out what the function does.

You may be wondering why we would want to display a window in the first place when our game will not have any visual elements. The answer is that on Microsoft Windows, any program that handles keyboard input should display a window. Our game will only be able to react to keyboard input when our game window is active. This will also allow the player to switch back and forth between our game and other running applications.

Next, take a close look at the following line:
dynamic_menu menu;

If this confuses you, it might be helpful to realize that this is structurally similar to something we have seen earlier:
int answer;

Both of these statements are variable definitions. One of them defines a variable of type int and gives it the name "answer", the other one defines a variable of type dynamic_menu and gives it the name "menu". And just as int is a data type for numbers, dynamic_menu is a data type for menus. The word "dynamic" is used here meaning "flexible" because you, as game programmer, can decide which items the menu will contain.

Let's go ahead and add some items to our menu:
menu.add_item_tts("Start game");
menu.add_item_tts("Keyboard practice");
menu.add_item_tts("Exit game");

Notice the striking similarity to function calls? The only difference is that this time we are calling a function that lives inside our menu variable. Recall that an object is a value with variables and functions living inside it. In this case we have on our hands an object of type dynamic_menu.

To call a function inside an object, we write the variable name, a full stop, and then the name of the function we are calling. This is followed by the usual pair of parentheses which, as we have learned, is part of every function call. A function inside an object is called a method. To use this term in context: add_item_tts is a method of the menu object.

An object gets its methods from its type. Thus, to find out what methods an object provides we need only know what type, or class, the object belongs to. The "menu" object is of class dynamic_menu, so we need only consult the reference of dynamic_menu to find out about the available methods.

Exercise:

The following statement defines an object variable:
file saved_game;
Which of the following statements are valid?
saved_game.open("test.dat", "w");
saved_game.print("Hello, world!");
saved_game.write("Hello again!");
saved_game.close();
saved_game.open("test2.dat");

With the menu set up the way it is, we need to perform one more step to present it to the user:

menu.run("Please choose a menu item with the arrow keys, then hit enter to activate it.", true);

3.6 Sound and speech


This section covers two additional building blocks required for putting Memory Train together. Let us begin with sound, which will be an essential feature of any audio game you will ever develop.

In BGT, every sound is represented by a sound object. One way of visualizing a sound object is to picture it as a tape player with its various controls. For example, while your tape player probably has buttons labelled play, stop, and pause, as well as a volume knob or slider, the sound object has methods named play, stop, and pause, as well as a volume property. In addition, a program sometimes needs to check if a sound is still playing. To allow this, the sound object has a property called playing which will be true as long as the sound is playing, and will become false as soon as it has stopped. The following example illustrates how the sound object can be used:

void main()
{
sound intro; // Creates the sound object.
intro.load("intro.wav"); // Loads the sound; equivalent to putting a tape into the player.
intro.play(); // Starts playing.
while(intro.playing)
{
// Do something while the intro is playing.
wait(5); // Give other Windows tasks 5 milliseconds time.
}
// The sound has stopped.
}

Some games, including Memory Train, make use of a speech synthesizer on the player's computer. The way to do this in BGT is to use a tts_voice object, where tts stands for "text to speech." The two most interesting methods of tts_voice are speak and speak_wait. The speak method will begin speaking the string which was passed to it and will continue speaking in the background while your program may do other things. In comparison, the speak_wait method will additionally wait until the speech has stopped, and only then will your program continue with the next instruction.

3.7 Time


All but the simplest of games need to keep track of time in some way. BGT provides a timer object for this purpose. The most important features of the timer object are the restart method and the elapsed property. By calling the restart method, your program is able to reset the timer back to zero in much the same way as if you were restarting a stopwatch. The elapsed property will always contain the number of milliseconds since the timer was last restarted, or, if it was never restarted, elapsed will contain the number of milliseconds since the timer was created.

It is considered good behavior for a Windows application to regularly wait for a few milliseconds. Not only does this give other processes in the system more time to execute their own tasks but it also increases the system's ability to save energy. In BGT, you can use the wait function to put your program to sleep for the specified number of milliseconds. Note that the time which actually passes may differ from the time you specified, so it is advised that you use a timer to check how many milliseconds have actually passed. When in doubt, do not use the wait function to time game events, but use a timer instead as timers are usually accurate to the millisecond.

3.8 Keyboard input


In order for your game to be able to respond to keyboard input, BGT contains two functions which let you check the status of any given key on the user's keyboard. Use the key_down function to find out if a given key is being held down at that moment. Note that key_down will return true as long as the key is being held down. If you are not interested in how long a key is being held but rather would like to be informed of every keypress just once, use the key_pressed function instead. When a key is held down, key_pressed will return true only the first time you check that particular key, and false on subsequent calls. Only if the key was released and is now being held down once more will key_pressed return true again.

To find out which keys can be checked and how they are expressed in BGT, consult appendix A of the BGT help system.

3.9 The main function


Armed with a basic understanding of the fundamental features of BGT, we can now begin coding the bulk of Memory Train. Let us start with the main function which, as you learned earlier in this tutorial, is the heart of any BGT script. The responsibilities of the main function are as follows:

While we are at it, we will also define some global variables the main function refers to. Our code now looks as follows:

#include "dynamic_menu.bgt"

// Sound objects.
sound music;
sound error_sound;
sound[] tone(4); // The four tones used for sequences.

void main()
{
// Load the four tones.
tone[0].load("1.wav");
tone[0].volume = -10;
tone[1].load("2.wav");
tone[1].volume = -10;
tone[2].load("3.wav");
tone[2].volume = -10;
tone[3].load("4.wav");
tone[3].volume = -10;

// Load the error sound.
error_sound.load("error.wav");
error_sound.volume = -10;

// Load the music.
music.load("music.wav");
music.volume = -10;

// Set up voice.
tts_voice voice;

// Set up menu.
dynamic_menu menu;
menu.allow_escape = true;
menu.wrap = true;
menu.add_item_tts("Start game");
menu.add_item_tts("Keyboard practice");
menu.add_item_tts("Exit game");

// Show game window and speak welcome message.
show_game_window("Memory Train");
voice.speak_wait("Welcome to Memory Train!");

// Loop the music in the background before running the menu.
music.play_looped();

int choice; // This stores the user's menu selections.

do
{
choice = menu.run("Please choose a menu item with the arrow keys, then hit enter to activate it.", true);
if(choice==1)
{
music.stop();
play_round(); // We will define this function later.
music.play_looped();
}
else if(choice==2)
{
music.stop();
keyboard_practice(); // We will define this function later.
music.play_looped();
}
}
while(choice!=0 and choice!=3);

// The user pressed escape or chose to exit.
voice.speak_wait("Thanks for playing.");
}

3.10 Keyboard practice


If the user chooses the keyboard practice item from the menu, our above main function will call a function named keyboard_practice. This has the following responsibilities:

Here is the code for the keyboard_practice function:

void keyboard_practice()
{
tts_voice voice;
voice.speak_wait("Press the arrow keys to find out which key generates which tone.");
voice.speak_wait("Press escape to stop practicing.");
while(!key_pressed(KEY_ESCAPE))
{
if(key_pressed(KEY_LEFT))
{
play_tone(0);
}
else if(key_pressed(KEY_DOWN))
{
play_tone(1);
}
else if(key_pressed(KEY_RIGHT))
{
play_tone(2);
}
else if(key_pressed(KEY_UP))
{
play_tone(3);
}
wait(5);
}
}

3.11 Playing a tone


Notice how we introduced the play_tone function in the above code for keyboard_practice. The responsibility of play_tone is to play a single tone. Here is the corresponding code:

void play_tone(int i)
{
tone[i].stop();
tone[i].play();
}

This function first stops the tone in case it is already playing, then restarts playing it. By the way, if the expression tone[i] confuses you, this might be a good time to read up on the subject of arrays in the language tutorial.

3.12 Playing the game


If the user chooses the "start game" item from our game menu, the main function calls the play_round function. This function is responsible for carrying out an entire round of play, after which the score is announced. Here is the code for play_round:

void play_round()
{
// Initialize game state.
bool game_over = false; // The game will continue as long as this is false.
int[] sequence; // The running sequence.
int sequence_length = 0;
float time_between_tones = 500; // The initial speed at which tones are played, in milliseconds.
float time_between_inputs = 2000; // Maximum time to input next key before player gets bounced.
do
{
// Add another tone to the sequence.
sequence_length++;
sequence.resize(sequence_length);
sequence[sequence_length-1] = random(0,3);
// Play back the sequence from start to finish.
output_sequence(@sequence, time_between_tones);
// Let them repeat it if they can.
game_over = input_sequence(@sequence, time_between_inputs);
// Every time another five tones have been mastered:
// increase the speed ever so slightly.
if((sequence_length%5) == 0)
{
time_between_tones = time_between_tones*0.9;
// Make sure it does not fall below 150.
if(time_between_tones < 150)
{
time_between_tones = 150;
}
}
}
while(!game_over);
int score = sequence_length-1; // minus 1 because they failed on the last.
tts_voice voice;
voice.speak_wait("Your final score was " + score);
}

3.13 Playing a sequence


The output_sequence function plays a sequence of tones so that the player may try to memorize it. It requires two parameters, the sequence to output, and the time between the tones. This flexible approach was taken to enable the game to increase the speed over time. Here is the code for output_sequence:

void output_sequence(int[]@ sequence, float time_between_tones)
{
timer clock;
int current;
for(uint i=0; i<sequence.length(); i++)
{
// For every tone but the first, wait before playing.
if(i>0)
{
clock.restart();
while(clock.elapsed < time_between_tones)
{
wait(5);
}
}
current = sequence[i];
play_tone(current);
}
// Finally, wait for the last sound to subside.
while(tone[current].playing)
{
wait(5);
}
}

3.14 Testing the player's memory


The final puzzle piece is the input_sequence function. This function is responsible for testing if the player remembers the sequence correctly. If the player fails to press one of the arrow keys in a given time, or presses the wrong arrow key, this function will return true to indicate that the game is over. If, on the other hand, the player manages to replay the sequence correctly, then this function returns false, indicating that the game is to continue. Here is the code for input_sequence:

bool input_sequence(int[] @sequence, float time_between_inputs)
{
timer clock;
for(uint i=0; i<sequence.length(); i++)
{
// Do the following for every tone in the sequence:
clock.restart();
int input = -1; // Set it to something invalid.
while(clock.elapsed < time_between_inputs)
{
if(key_pressed(KEY_LEFT))
{
input = 0;
}
else if(key_pressed(KEY_DOWN))
{
input = 1;
}
else if(key_pressed(KEY_RIGHT))
{
input = 2;
}
else if(key_pressed(KEY_UP))
{
input = 3;
}
if(input>=0)
{
break; // Stop waiting because something was typed.
}
wait(5);
}
// Game is over if timed out or wrong key.
if(input!=sequence[i])
{
error_sound.play_wait();
return true;
}
// Play back successful tones as feedback
play_tone(input);
}

// The player managed to replay the entire sequence.
// Wait one second to let them catch their breath.
clock.restart();
while(clock.elapsed < 1000)
{
wait(5);
}
return false;
}

3.15 Putting it all together


The complete source code for Memory Train is contained in the code fragments given in sections 3.9 to 3.14. In order to test the game, you can simply paste all of the code fragments into the file memory_train.bgt which you created earlier. To run the game, simply navigate to the file memory_train.bgt in Windows Explorer, then press enter to start.

Final exercises: